package com.zhangqiang.git.auth2secrity.framework.oauth2;

import com.zhangqiang.git.auth2secrity.framework.config.OAuth2Config;
import com.zhangqiang.git.auth2secrity.framework.security.service.CustomTokenEnhancer;
import com.zhangqiang.git.auth2secrity.framework.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
 * @ClassName: OAuth2AuthorizationServer
 * @Description: 认证服务
 * @author: zhangqiang
 * @date: 2021/3/1
 * 
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
	/**
	 * redis工厂，默认使用lettue
	 */
	@Autowired
	public RedisConnectionFactory redisConnectionFactory;

	@Autowired
	@Lazy
	private BCryptPasswordEncoder bCryptPasswordEncoder;

	@Autowired
	private CustomUserDetailsService userDetailsService;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private OAuth2Config oAuth2Config;


	/**
	 * 用来配置令牌端点(Token Endpoint)的安全约束
	 * @param security
	 * @throws Exception
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security)
			throws Exception {
		// code授权添加
		security.tokenKeyAccess("permitAll()")
				.checkTokenAccess("isAuthenticated()")
				.allowFormAuthenticationForClients();
	}

	/**
	 * 引入自定义userDetail
	 *
	 * @param endpoints
	 * @throws Exception
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.userDetailsService(userDetailsService);
		endpoints.tokenStore(tokenStore());
        endpoints.tokenEnhancer(tokenEnhancer());

		//密码模式必须添加authenticationManager
		endpoints.authenticationManager(authenticationManager);
	}

	/**
	 * 用来配置客户端详情服务（ClientDetailsService），
	 * 客户端详情信息在这里进行初始化，
	 * 你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
	 * @param clients
	 * @throws Exception
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clients)
			throws Exception {

		// 认证客户端目前为固定值,后续可以做成配置文件或者数据库获取方式（注意：可以配置多个客户端）
		// client = oauth2_id_20210222
		// secret = oauth2_secret_20210222
		clients.inMemory().withClient(oAuth2Config.getOauth2_clientId())
				.secret(bCryptPasswordEncoder.encode(oAuth2Config.getOauth2_clientSecret()))
				.scopes("all")
				.authorizedGrantTypes(
						"authorization_code",
						"client_credentials",
						"password",
						"refresh_token"
				)
				.resourceIds("oauth2-resource")
				// code授权回调地址
				.redirectUris("https://open.bot.tmall.com/oauth/callback")
				.accessTokenValiditySeconds(5000)
				.refreshTokenValiditySeconds(50000)
				//.and()可以设置多个客户端
				;
	}


	public TokenStore tokenStore() {
		if(oAuth2Config.isRedis_enable()){
			//使用redis存储token
			RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
			//设置redis token存储中的前缀
			redisTokenStore.setPrefix("auth-token:");
			return redisTokenStore;
		}
		return new InMemoryTokenStore();// 内存存储
	}

	@Bean
	public DefaultTokenServices tokenService() {
		DefaultTokenServices tokenServices = new DefaultTokenServices();
		//配置token存储
		tokenServices.setTokenStore(tokenStore());
		//开启支持refresh_token，此处如果之前没有配置，启动服务后再配置重启服务，可能会导致不返回token的问题，解决方式：清除redis对应token存储
		tokenServices.setSupportRefreshToken(true);
		//复用refresh_token
		tokenServices.setReuseRefreshToken(true);
		//token有效期，设置12小时
		tokenServices.setAccessTokenValiditySeconds(12 * 60 * 60);
		//refresh_token有效期，设置一周
		tokenServices.setRefreshTokenValiditySeconds(7 * 24 * 60 * 60);
		return tokenServices;
	}

	/**
	 * token 生成规则
	 * @return
	 */
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }

}